<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			// ES5 的对象属性名都是字符串，这容易造成属性名的冲突。
			// 比如，你使用了一个他人提供的对象，但又想为这个对象添加
			// 新的方法（mixin 模式），新方法的名字就有可能与现有方法
			// 产生冲突。如果有一种机制，保证每个属性的名字都是独一无二的
			// 就好了，这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。
			// ES6 引入了一种新的原始数据类型Symbol，表示独一无二的值。它
			// 是 JavaScript 语言的第七种数据类型，前六种
			// 是：undefined、null、布尔值（Boolean）、字符串（String）、数值（Number）、对象（Object）。
			// Symbol 值通过Symbol函数生成。这就是说，对象的属性名现在可
			// 以有两种类型，一种是原来就有的字符串，另一种就是新增
			// 的 Symbol 类型。凡是属性名属于 Symbol 类型，就都是独一无二的，
			// 可以保证不会与其他属性名产生冲突。
			// 注意，Symbol函数前不能使用new命令，否则会报错。这是因为生成
			// 的 Symbol 是一个原始类型的值，不是对象。也就是说，由于 Symbol 值
			// 不是对象，所以不能添加属性。基本上，它是一种类似于字符串的数据类型。
			// Symbol函数可以接受一个字符串作为参数，表示对 Symbol 实例的描述，
			// 主要是为了在控制台显示，或者转为字符串时，比较容易区分。
			//Symbol 值作为属性名时，该属性还是公开属性，不是私有属性。
			let s1 = Symbol('s1')
			let s2 = Symbol('s2')
			console.log(s1)//Symbol(s1)
			console.log(s2)//Symbol(s2)




			// 如果 Symbol 的参数是一个对象，就会调用该对象的toString方法，
			//将其转为字符串，然后才生成一个 Symbol 值。
			const obj = {
				toString() {
					return 'abc';
				}
			};
			const sym = Symbol(obj);
			console.log(sym);
			



			//注意，Symbol函数的参数只是表示对当前 Symbol 值的描述，
			//因此相同参数的Symbol函数的返回值是不相等的。
			// 没有参数的情况
			let s1 = Symbol();
			let s2 = Symbol();
			s1 === s2 // false

			// 有参数的情况
			let s1 = Symbol('foo');
			let s2 = Symbol('foo');
			s1 === s2 // false
			



			//Symbol 值不能与其他类型的值进行运算，会报错。
			let sym = Symbol('My symbol');
			"your symbol is " + sym
			// TypeError: can't convert symbol to string
			`your symbol is ${sym}`
			// TypeError: can't convert symbol to string




			//但是，Symbol 值可以显式转为字符串
			let sym = Symbol('My symbol');
			String(sym) // 'Symbol(My symbol)'
			sym.toString() // 'Symbol(My symbol)'
			



			//Symbol 值也可以转为布尔值，但是不能转为数值。
			let sym = Symbol();
			Boolean(sym) // true
			!sym  // false
			Number(sym) // TypeError
			sym + 2 // TypeError




			//实例属性description，直接返回 Symbol 的描述。
			const sym = Symbol('foo');
			sym.description // "foo"




			// 由于每一个 Symbol 值都是不相等的，这意味着 Symbol 
			// 值可以作为标识符，用于对象的属性名，就能保证不会出现
			// 同名的属性。这对于一个对象由多个模块构成的情况非常有用，
			// 能防止某一个键被不小心改写或覆盖。
			let mySymbol = Symbol();
			// 第一种写法
			let a = {};
			a[mySymbol] = 'Hello!';
			console.log(a[mySymbol]);//Hello
			


			
			//Symbol 值作为对象属性名时，不能用点运算符。
			const mySymbol = Symbol();
			const a = {};
			a.mySymbol = 'Hello!';
			a[mySymbol] // undefined
			a['mySymbol'] // "Hello!"




			//在对象的内部，使用 Symbol 值定义属性时，Symbol 值必须放在方括号之中
			let s = Symbol();
			let obj = {
				[s](arg) {}
			};
			obj[s](123);


			
			
			//Symbol 类型还可以用于定义一组常量，保证这组常量的值都是不相等的
			const log = {};
			log.levels = {
			  DEBUG: Symbol('debug'),
			  INFO: Symbol('info'),
			  WARN: Symbol('warn')
			};
			console.log(log.levels.DEBUG, 'debug message');//Symbol(debug) "debug message"
			console.log(log.levels.INFO, 'info message');//Symbol(info) "info message"
			
			

			//消除魔术字符串
			//魔术字符串指的是，在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。
			//风格良好的代码，应该尽量消除魔术字符串，改由含义清晰的变量代替。
			function getArea(shape, options) {
				let area = 0;
				switch (shape) {
					case 'Triangle': // 魔术字符串
				}
			}
			getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
			//上面代码中字符串Triangle就是一个魔术字符串。它多次出现，与代码形成“强耦合”，不利于将来的修改和维护。
			//常用的消除魔术字符串的方法，就是把它写成一个变量。
			const shapeType = {
				triangle: 'Triangle'
			};
			function getArea(shape, options) {
				switch (shape) {
					case shapeType.triangle:
				}
			}
			getArea(shapeType.triangle, { width: 100, height: 100 });
			//hapeType.triangle等于哪个值并不重要，只要确保不会跟其他shapeType属性的
			//值冲突即可。因此，这里就很适合改用 Symbol 值。
			const shapeType = {
				triangle: Symbol()
			};




			// /属性名的遍历
			/*Symbol 作为属性名，遍历对象的时候，该属性不会出现在for...in、for...of循环中，
			也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
			但是，它也不是私有属性，有一个Object.getOwnPropertySymbols()方法，可
			以获取指定对象的所有 Symbol 属性名。该方法返回一个数组，
			成员是当前对象的所有用作属性名的 Symbol 值。*/
			const obj = {};
			let a = Symbol('a');
			let b = Symbol('b');
			obj[a] = 'Hello';
			obj[b] = 'World';
			const objectSymbols = Object.getOwnPropertySymbols(obj);
			objectSymbols
			// [Symbol(a), Symbol(b)]

			//另一个新的 API，Reflect.ownKeys()方法可以返回所有类型的键名，包括常规键名和 Symbol 键名
			let obj = {
				[Symbol('my_key')]: 1,
				enum: 2,
				nonEnum: 3
			};
			Reflect.ownKeys(obj)
			//  ["enum", "nonEnum", Symbol(my_key)]
			//由于以 Symbol 值作为键名，不会被常规方法遍历得到。我们可以利用这个特性，
			//为对象定义一些非私有的、但又希望只用于内部的方法。



			


			//Symbol.for()，Symbol.keyFor()
			//有时，我们希望重新使用同一个 Symbol 值，Symbol.for()方法可以做到这一点。
			//它接受一个字符串作为参数，然后搜索有没有以该参数作为名称的 Symbol 值。如果有，
			//就返回这个 Symbol 值，否则就新建一个以该字符串为名称的 Symbol 值，并将其注册到全局。
			let s1 = Symbol.for('foo');
			let s2 = Symbol.for('foo');
			s1 === s2 // true
			//s1和s2都是 Symbol 值，但是它们都是由同样参数的Symbol.for方法生成的，所以实际上是同一个值。

			/*Symbol.for()与Symbol()这两种写法，都会生成新的 Symbol。它们的区别是，
			前者会被登记在全局环境中供搜索，后者不会。Symbol.for()不会每次调用就返
			回一个新的 Symbol 类型的值，而是会先检查给定的key是否已经存在，如果不存
			在才会新建一个值。比如，如果你调用Symbol.for("cat")30 次，每次都会返回
			同一个 Symbol 值，但是调用Symbol("cat")30 次，会返回 30 个不同的 Symbol 值。*/
			Symbol.for("bar") === Symbol.for("bar")
			// true
			Symbol("bar") === Symbol("bar")
			// false

			//Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
			let s1 = Symbol.for("foo");
			Symbol.keyFor(s1) // "foo"
			let s2 = Symbol("foo");
			Symbol.keyFor(s2) // undefined
			//变量s2属于未登记的 Symbol 值，所以返回undefined。

			//Symbol.for()为 Symbol 值登记的名字，是全局环境的，不管有没有在全局环境运行。
			function foo() {
				return Symbol.for('bar');
			}
			const x = foo();
			const y = Symbol.for('bar');
			console.log(x === y); // true

			//Symbol.for()的这个全局登记特性，可以用在不同的 iframe 或 service worker 中取到同一个值。
			iframe = document.createElement('iframe');
			iframe.src = String(window.location);
			document.body.appendChild(iframe);
			iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
			// true
			//rame 窗口生成的 Symbol 值，可以在主页面得到。

			































		</script>
	</body>
</html>
